package net.zwj.compile;

import java.util.EnumSet;

import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementScanner6;
import javax.tools.Diagnostic.Kind;

/**
 * 检查程序命名规范的编译器插件:<br/>
 * 如果程序命名不规范，将会输出一个警告信息
 * 
 */
public class NameChecker {

	private final Messager messager;
	NameCheckScanner nameCheckScanner = new NameCheckScanner();

	public NameChecker(ProcessingEnvironment processingEnv) {
		this.messager = processingEnv.getMessager();
	}

	/**
	 * 对Java程序中的命名进行检查
	 * <ul>
	 * <li>类或接口：符合驼式命名法，首字母大写
	 * <li>方法：符合驼式命名法，首字母小写
	 * <li>字段：
	 * <ul>
	 * <li>类、实例变量：符合驼式命名法，首字母小写
	 * <li>常量：全部大写
	 * </ul>
	 * </ul>
	 * 
	 * @param element
	 */
	public void checkNames(Element element) {
		nameCheckScanner.scan(element);
	}

	/**
	 * 名称检查器的实现类，继承JDK1.6中提供的ElementScanner6<br/>
	 * 将会以Visitor模式访问抽象语法树中的元素
	 */

	private class NameCheckScanner extends ElementScanner6<Void, Void> {
		/**
		 * 检查Java类
		 */
		@Override
		public Void visitType(TypeElement e, Void p) {
			scan(e.getTypeParameters(), p);
			checkCamelCase("类",e, true);
			return super.visitType(e, p);
		}

		/**
		 * 检查方法名
		 */
		@Override
		public Void visitExecutable(ExecutableElement e, Void p) {
			if (e.getKind() == ElementKind.METHOD) {
				Name name = e.getSimpleName();
				if (name.contentEquals(e.getEnclosingElement().getSimpleName())) {
					messager.printMessage(Kind.WARNING, "一个普通方法" + name
							+ "不应该与类名重复,避免与构造函数产生混淆", e);
				}
				checkCamelCase("方法",e,false);
			}
			return super.visitExecutable(e, p);
		}

		/**
		 * 检查变量名
		 */
		@Override
		public Void visitVariable(VariableElement e, Void p) {
			if (e.getKind() == ElementKind.ENUM_CONSTANT
					|| e.getConstantValue() != null || heuristicallyConstant(e))
				checkAllCaps("变量",e);
			else
				checkCamelCase("变量",e, false);
			return super.visitVariable(e, p);
		}

		/**
		 * 判断一个变量是否是常量
		 * 
		 * @param e
		 * @return
		 */
		private boolean heuristicallyConstant(VariableElement e) {
			if (e.getEnclosingElement().getKind() == ElementKind.INTERFACE) {
				return true;
			} else if (e.getKind() == ElementKind.FIELD
					&& e.getModifiers().containsAll(
							EnumSet.of(Modifier.PUBLIC, Modifier.STATIC,
									Modifier.FINAL))) {
				return true;
			} else {
				return false;
			}
		}

		private void checkCamelCase(String pre,Element e, boolean initialCaps) {
			String name = e.getSimpleName().toString();
			boolean previousUpper = false;
			boolean conventional = true;
			int firstCodePoint = name.codePointAt(0);

			if (Character.isUpperCase(firstCodePoint)) {
				previousUpper = true;
				if (!initialCaps) {
					messager.printMessage(Kind.WARNING, pre + name
							+ "应该以小写字母开头", e);
					return;
				}
			} else if (Character.isLowerCase(firstCodePoint)) {
				previousUpper = false;
				if (initialCaps) {
					messager.printMessage(Kind.WARNING, pre + name
							+ "应该以大写字母开头", e);
					return;
				}
			} else {
				conventional = false;
			}

			if (conventional) {
				int cp = firstCodePoint;
				for (int i = Character.charCount(cp); i < name.length(); i += Character
						.charCount(cp)) {
					cp = name.codePointAt(i);
					if (Character.isUpperCase(cp)) {
						if (previousUpper) {
							conventional = false;
							break;
						}
						previousUpper = true;
					} else {
						previousUpper = false;
					}
				}
			}

			if (!conventional) {
				messager.printMessage(Kind.WARNING, pre + name + "应该符合驼式命名法",
						e);
				return;
			}
		}

		private void checkAllCaps(String pre,Element e) {
			String name = e.getSimpleName().toString();

			boolean conventional = true;
			int firstCodePoint = name.codePointAt(0);

			if (!Character.isUpperCase(firstCodePoint)) {
				conventional = false;
			} else {
				boolean previousUnderscore = false;
				int cp = firstCodePoint;
				for (int i = Character.charCount(cp); i < name.length(); i += Character
						.charCount(cp)) {
					cp = name.codePointAt(i);
					if (cp==(int)'_') {
						if (previousUnderscore) {
							conventional = false;
							break;
						}
						previousUnderscore = true;
					} else {
						previousUnderscore = false;
						if(!Character.isUpperCase(cp)&&!Character.isDigit(cp)){
							conventional = false;
							break;
						}
					}
				}
			}


			if (!conventional) {
				messager.printMessage(Kind.WARNING, pre+name
						+ "应当全部以大写字母或下划线命名，并且以字母开头", e);
				return;
			}
		}

	}

}
